#include "pch.h"
#include <settings_objects.h>
#include <common.h>
#include "powerpreview.h"
#include "trace.h"
#include "settings.h"
#include "Generated Files/resource.h"
#include <common\os-detect.h>

// Constructor
PowerPreviewModule::PowerPreviewModule() :
    m_moduleName(GET_RESOURCE_STRING(IDS_MODULE_NAME)),
    m_fileExplorerModules(
        { // SVG Preview Handler settings object.
          new PreviewHandlerSettings(
              true,
              L"svg-previewer-toggle-setting",
              GET_RESOURCE_STRING(IDS_PREVPANE_SVG_SETTINGS_DESCRIPTION),
              L"{ddee2b8a-6807-48a6-bb20-2338174ff779}",
              L"Svg Preview Handler",
              new RegistryWrapper()),

          // MarkDown Preview Handler Settings Object.
          new PreviewHandlerSettings(
              true,
              L"md-previewer-toggle-setting",
              GET_RESOURCE_STRING(IDS_PREVPANE_MD_SETTINGS_DESCRIPTION),
              L"{45769bcc-e8fd-42d0-947e-02beef77a1f5}",
              L"Markdown Preview Handler",
              new RegistryWrapper()),
          //SVG Thumbnail Provider settings object.
          new ThumbnailProviderSettings(
              true,
              L"svg-thumbnail-toggle-setting",
              GET_RESOURCE_STRING(IDS_SVG_THUMBNAIL_PROVIDER_SETTINGS_DESCRIPTION),
              L"{36B27788-A8BB-4698-A756-DF9F11F64F84}",
              L"Svg Thumbnail Provider",
              new RegistryWrapper(),
              L".svg\\shellex\\{E357FCCD-A995-4576-B01F-234630154E96}") })
{
    // Initialize the toggle states for each module
    init_settings();

    // If the user is on the new settings interface, File Explorer might be disabled if they updated from old to new settings, so initialize the registry state in the constructor as PowerPreviewModule::enable/disable will not be called on startup
    if (UseNewSettings())
    {
        update_registry_to_match_toggles();
    }
}

// Destroy the powertoy and free memory.
void PowerPreviewModule::destroy()
{
    Trace::Destroyed();
    delete this;
}

// Return the display name of the powertoy, this will be cached.
const wchar_t* PowerPreviewModule::get_name()
{
    return m_moduleName.c_str();
}

// Return JSON with the configuration options.
bool PowerPreviewModule::get_config(_Out_ wchar_t* buffer, _Out_ int* buffer_size)
{
    HINSTANCE hinstance = reinterpret_cast<HINSTANCE>(&__ImageBase);

    // Create a Settings object.
    PowerToysSettings::Settings settings(hinstance, get_name());

    // General Settings.
    settings.set_description(GET_RESOURCE_STRING(IDS_GENERAL_DESCRIPTION));
    settings.set_icon_key(GET_RESOURCE_STRING(IDS_ICON_KEY_NAME));
    settings.set_overview_link(L"https://aka.ms/PowerToysOverview_FileExplorerAddOns");

    // Preview Pane: Settings Group Header.
    settings.add_header_szLarge(
        GET_RESOURCE_STRING(IDS_PRVPANE_FILE_PREV_STTNGS_GROUP_HEADER_ID),
        GET_RESOURCE_STRING(IDS_PRVPANE_FILE_PREV_STTNGS_GROUP_DESC),
        GET_RESOURCE_STRING(IDS_PRVPANE_FILE_PREV_STTNGS_GROUP_TEXT));

    for (auto fileExplorerModule : this->m_fileExplorerModules)
    {
        settings.add_bool_toggle(
            fileExplorerModule->GetToggleSettingName(),
            fileExplorerModule->GetToggleSettingDescription(),
            fileExplorerModule->GetToggleSettingState());
    }

    return settings.serialize_to_buffer(buffer, buffer_size);
}

// Called by the runner to pass the updated settings values as a serialized JSON.
void PowerPreviewModule::set_config(const wchar_t* config)
{
    try
    {
        PowerToysSettings::PowerToyValues settings = PowerToysSettings::PowerToyValues::from_json_string(config);

        bool updateSuccess = true;
        bool isElevated = is_process_elevated(false);
        for (auto fileExplorerModule : this->m_fileExplorerModules)
        {
            // If the user is using the new settings interface, as it does not have a toggle to modify enabled consider File Explorer to always be enabled
            updateSuccess = updateSuccess && fileExplorerModule->UpdateState(settings, this->m_enabled || UseNewSettings(), isElevated);
        }

        if (!updateSuccess)
        {
            show_update_warning_message();
        }

        settings.save_to_settings_file();
    }
    catch (std::exception const& e)
    {
        Trace::SetConfigInvalidJSON(e.what());
    }
}

// Enable preview handlers.
void PowerPreviewModule::enable()
{
    // Should only be done for old settings as it is already done for new settings in the constructor.
    if (!UseNewSettings())
    {
        update_registry_to_match_toggles();
    }

    if (!this->m_enabled)
    {
        Trace::EnabledPowerPreview(true);
    }

    this->m_enabled = true;
}

// Disable active preview handlers.
void PowerPreviewModule::disable()
{
    elevation_check_wrapper([this]() {
        for (auto fileExplorerModule : this->m_fileExplorerModules)
        {
            fileExplorerModule->Disable();
        }
    });

    if (this->m_enabled)
    {
        Trace::EnabledPowerPreview(false);
    }

    this->m_enabled = false;
}

// Returns if the powertoys is enabled
bool PowerPreviewModule::is_enabled()
{
    return this->m_enabled;
}

// Load the settings file.
void PowerPreviewModule::init_settings()
{
    try
    {
        // Load and parse the settings file for this PowerToy.
        PowerToysSettings::PowerToyValues settings =
            PowerToysSettings::PowerToyValues::load_from_settings_file(PowerPreviewModule::get_name());

        // Load settings states.
        for (auto fileExplorerModule : this->m_fileExplorerModules)
        {
            fileExplorerModule->LoadState(settings);
        }
    }
    catch (std::exception const& e)
    {
        Trace::InitSetErrorLoadingFile(e.what());
    }
}

// Function to check if the registry states need to be updated
bool PowerPreviewModule::is_registry_update_required()
{
    for (auto fileExplorerModule : this->m_fileExplorerModules)
    {
        if (fileExplorerModule->GetToggleSettingState() != fileExplorerModule->CheckRegistryState())
        {
            return true;
        }
    }

    return false;
}

// Function to warn the user that PowerToys needs to run as administrator for changes to take effect
void PowerPreviewModule::show_update_warning_message()
{
    // Show warning message if update is required
    MessageBoxW(NULL,
                GET_RESOURCE_STRING(IDS_FILEEXPLORER_ADMIN_RESTART_WARNING_DESCRIPTION).c_str(),
                GET_RESOURCE_STRING(IDS_FILEEXPLORER_ADMIN_RESTART_WARNING_TITLE).c_str(),
                MB_OK | MB_ICONWARNING);
}

// Function that checks if a registry method is required and if so checks if the process is elevated and accordingly executes the method or shows a warning
void PowerPreviewModule::registry_and_elevation_check_wrapper(std::function<void()> method)
{
    // Check if a registry update is required
    if (is_registry_update_required())
    {
        elevation_check_wrapper(method);
    }
}

// Function that checks if the process is elevated and accordingly executes the method or shows a warning
void PowerPreviewModule::elevation_check_wrapper(std::function<void()> method)
{
    // Check if the process is elevated in order to have permissions to modify HKLM registry
    if (is_process_elevated(false))
    {
        method();
    }
    // Show a warning if it doesn't have permissions
    else
    {
        show_update_warning_message();
    }
}

// Function that updates the registry state to match the toggle states
void PowerPreviewModule::update_registry_to_match_toggles()
{
    registry_and_elevation_check_wrapper([this]() {
        for (auto fileExplorerModule : this->m_fileExplorerModules)
        {
            if (fileExplorerModule->GetToggleSettingState())
            {
                // Enable all the modules with initial state set as true.
                fileExplorerModule->Enable();
            }
            else
            {
                fileExplorerModule->Disable();
            }
        }
    });
}
